'''
gViews - for the Random Game Generator project            
By Doctus (kirikayuumura.noir@gmail.com)

Actions which occur in response to user commands.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
'''
import time, random, os, base64
import gTile, gDockWidget, gDialogs, gMenuBar, gSystem, gGame
from gRPC import server, client, serverRPC, clientRPC
from gJson import jsondump, jsonload
from gSystem import cameraPosition, setCameraPosition, mainWindow
from gSystem import translate, showErrorMessage
from gSystem import showPopupMenuAt, IMAGE_FILTER
from gSystem import promptString, promptInteger, promptCoordinates, promptSaveFile, promptLoadFile
from gSystem import getZoom
from gDialogs import hostDialog, joinDialog
import gDialogs, gFaction
from PyQt4 import QtCore, QtGui

# Button enum
BUTTON_LEFT = 0
BUTTON_MIDDLE = 1
BUTTON_RIGHT = 2
BUTTON_CONTROL = 3
BUTTON_SHIFT = 6

# Feel free to add fields to the user object
class User(object):
    """User representation on the server."""
    
    def __init__(self, username):
        self.username = username
    
    def __repr__(self):
        return u"User(u'{name}')".format(name=self.username)
    
    def __unicode__(self):
        return self.username
    
    def __str__(self):
        return self.__unicode__()

class _state(object):
    """A state class build to avoid all these global statements."""
    
    mouseButton = None
    mousePosition = (0, 0)
    session = None
    selectedUnit = None
    isHost = False
    
    @staticmethod
    def initialize():
        _state.menu = gMenuBar.menuBar()
        
        _state.awidget = gDockWidget.unitInfoWidget(mainWindow)
        _state.cwidget = gDockWidget.chatWidget(mainWindow)
        _state.gwidget = gDockWidget.gameControlWidget(mainWindow)
        _state.uwidget = gDockWidget.userListWidget(mainWindow)

        _state.users = {}
        _state.localuser = User(client.username)
        _state.users[client.username] = _state.localuser

def nextTurn():
    _state.gwidget.advanceTurn()
    _state.uwidget.advanceTurn()
    _state.session.advanceTurn()
    updateUnitInfo()

# MENUS

def showDomestic():
    domMenu = gDialogs.DomesticMenu()
    result = domMenu.display(mainWindow)

def showPersonnel():
    persMenu = gDialogs.PersonnelMenu()
    result = persMenu.display(mainWindow)

def showEconomic():
    ecoMenu = gDialogs.EconomicMenu()
    result = ecoMenu.display(_state.session, localhandle(), mainWindow)

def showDiplomacy():
    diploMenu = gDialogs.DiplomacyMenu()
    result = diploMenu.display(_state.session, localhandle(), mainWindow)
    
def showEspionage():
    espMenu = gDialogs.EspionageMenu()
    result = espMenu.display(mainWindow)

# MESSAGES

def say(message):
    """Say an IC message. This documentation is a lie."""
    _state.cwidget.insertMessage(message)

def announce(message):
    """Say an OOC message. This documentation is a lie."""
    _state.cwidget.insertMessage(message)
    
def ICSay(message):
    _state.icwidget.insertMessage(message)

def linkedName(name):
    return translate('views', '<a href="/tell {name}" title="{name}">{name}</a>').format(name=name)

def addUserToList(name, host=False):
    _state.uwidget.addUser(name, host)

# NETWORK (Server only)

def allusers():
    """Get a list of all users."""
    return _state.users.values()

def allusersbut(usernameOrUser):
    user = getuser(usernameOrUser)
    if not user:
        raise RuntimeError("No user named {user}.".format(user=idOrNameOrUser))
    all = allusers()
    assert(user in all)
    all.remove(user)
    return all

def getuser(username):
    """Returns a user given a username, or None if not valid."""
    if isinstance(username, User):
        assert(username in allusers())
        return username
    username = unicode(username)
    if server.userExists(username):
        username = server.fullname(username)
        assert(username in _state.users)
        return _state.users[username]
    return None

def usernames():
    """Returns all the usernames."""
    return _state.users.keys()

def localuser():
    """The user for the local player."""
    return _state.localuser

def localhandle():
    return localuser().username

def adduser(user):
    """Add a user to the list locally."""
    assert(user.username not in _state.users)
    _state.users[user.username] = user
    return user

def renameuser(oldname, newname):
    """Rename a user locally."""
    if oldname == newname:
        return
    user = removeuser(oldname)
    server.rename(oldname, newname)
    user.username = newname
    adduser(user)

def removeuser(username):
    """Remove a user from the list locally."""
    assert(username in _state.users)
    user = _state.users[username]
    del _state.users[username]
    return user

def getNetUserList():
    """Returns the user names formatted for transfer over net."""
    return _state.uwidget.getUsers()

def clearUserList():
    _state.uwidget.clearUserList()
    
@serverRPC
def respondUserRemove(name):
    _state.uwidget.removeUser(name)
    
@serverRPC
def respondUserList(list):
    for item in list:
        addUserToList(item[0], item[1])
    
@serverRPC
def respondMoveOrder(unitID, targ, origin):
    if not _state.uwidget.isDone(origin) and _state.session.getOwnerByID(unitID) == origin and _state.session.checkCanMove(unitID, targ):
        _state.session.armyMoveOrder(unitID, targ)

@clientRPC
def sendMoveOrder(user, unitID, targ):
    respondMoveOrder(allusersbut(user), unitID, targ, user.username)

@serverRPC
def respondSessionCreate():
    sess = gGame.GameSession.load(None)
    _state.session = sess
    
    dialog = gDialogs.newFactionDialog()
    
    def accept():
        valid = dialog.is_valid()
        if not valid:
            showErrorMessage(dialog.error)
        return valid
    
    if dialog.exec_(mainWindow, accept):
        result = dialog.save()
        createFaction(result)

@clientRPC
def sendSessionCreate(user):
    respondSessionCreate(allusersbut(user))

@serverRPC
def respondMapCreate(mappe, initunits, initcities):
    _state.session.loadMap(mappe)
    _state.session.loadUnits(initunits)
    _state.session.loadCities(initcities)
    
@clientRPC
def sendMapCreate(user, mappe, initunits, initcities):
    respondMapCreate(allusersbut(user), mappe, initunits, initcities)
    
def createMap():
    _state.session.generateMap()
    _state.session.generateStartingStuff()
    sendMapCreate(_state.session.map.dump(), _state.session.dumpUnits(), _state.session.dumpCities())
    
@serverRPC
def respondFactionCreate(uza, dat):
    _state.session.addFaction(uza, gFaction.Faction.loadFromStuff(dat))
    if _state.isHost and _state.session.getNumberOfFactions() == _state.uwidget.getNumberOfPlayers():
        createMap()
    
@clientRPC
def sendFactionCreate(user, dat):
    respondFactionCreate(allusersbut(user), user.username, dat)
    
def createFaction(dat):
    _state.session.addFaction(localhandle(), gFaction.Faction.loadFromStuff(dat))
    sendFactionCreate(dat)
    if _state.isHost and _state.session.getNumberOfFactions() == _state.uwidget.getNumberOfPlayers():
        createMap()
    
@serverRPC
def respondTurnComplete(origin):
    _state.uwidget.finishTurn(origin)
    if _state.uwidget.allDone():
        nextTurn()
    
@clientRPC
def sendTurnComplete(user):
    respondTurnComplete(allusers(), user.username)

def issueMoveOrder(unitID, targ):
    if (not _state.uwidget.isDone(localhandle())) and _state.session.getOwnerByID(unitID) == localhandle() and _state.session.checkCanMove(unitID, targ):
        _state.session.armyMoveOrder(unitID, targ)
        sendMoveOrder(unitID, targ)

def createSession():
    if _state.isHost:
        _state.session = gGame.GameSession()
        sendSessionCreate()
        
        dialog = gDialogs.newFactionDialog()
    
        def accept():
            valid = dialog.is_valid()
            if not valid:
                showErrorMessage(dialog.error)
            return valid
        
        if dialog.exec_(mainWindow, accept):
            result = dialog.save()
            createFaction(result)
        
    else:
        say(translate('views', "Sorry, you must be hosting to create a new session."))

def hostGame():
    """Allows the user to host a game."""
    if client.isConnected:
        say(translate('views', "You are already in a game."))
        return
    
    dialog = hostDialog()
    
    def accept():
        valid = dialog.is_valid()
        if not valid:
            showErrorMessage(dialog.error)
        return valid
    
    if dialog.exec_(mainWindow, accept):
        connection = dialog.save()
        renameuser(localhandle(), connection.username)
        if client.host(connection.port):
            say(translate('views', 'Now listening on port {port}.').format(port=connection.port))
            addUserToList(localhandle(), True)
            _state.isHost = True
        else:
            say(translate('views', 'Unable to access network; perhaps the port is in use?'))
        

def joinGame():
    """Allows the user to join a game."""
    if client.isConnected:
        say(translate('views', "You are already in a game."))
        return
    
    dialog = joinDialog()
    
    def accept():
        valid = dialog.is_valid()
        if not valid:
            showErrorMessage(dialog.error)
        return valid
    
    if dialog.exec_(mainWindow, accept):
        connection = dialog.save()
        renameuser(localhandle(), connection.username)
        client.join(connection.host, connection.port)
        say(translate('views', 'Connecting to {host}:{port}...').format(host=connection.host, port=connection.port))

def killConnection():
    """Kills the connection without reporting anything."""
    client.close()
    assert(localhandle() in usernames())
    assert(localuser() == getuser(localhandle()))
    users = {localhandle(): localuser()}

def disconnectGame():
    """Allows the user to disconnect from the internet."""
    if not client.isConnected:
        say(translate('views', "You are not connected."))
        return
    
    killConnection()
    say(translate('views', "Disconnected."))

# GUI

def showArmyInfo():
    _state.awidget.show()
    
def hideArmyInfo():
    _state.awidget.uberHide()

def updateUnitInfo():
    if _state.selectedUnit is not None:
        _state.awidget.updateInfo(_state.selectedUnit, mainWindow, _state.session.getFactionByHandle(localhandle()).getName())
    
def selectUnit(unit):
    _state.selectedUnit = unit
    updateUnitInfo()

def reportCamera():
    """Reports the current camera coordinates."""
    say(translate('views', 'x: {0}\ny: {1}', 'formats camera reporting.').format(*cameraPosition()))

# MOUSE ACTIONS

def mouseDrag(screenPosition, maploc, displacement):
    if _state.mouseButton == BUTTON_LEFT:
        setCameraPosition(map(lambda c, d,  z: c + d*z, cameraPosition(), displacement, (getZoom(), getZoom())))
        return
    elif _state.mouseButton == BUTTON_RIGHT:
        setCameraPosition(map(lambda c, d,  z: c + d*z, cameraPosition(), displacement, (getZoom(), getZoom())))

def mouseMove(screenPosition, maploc, displacement):
    if _state.mouseButton == BUTTON_LEFT or _state.mouseButton == BUTTON_RIGHT:
        return mouseDrag(screenPosition, maploc, displacement)

def mousePress(screenPosition, maploc, button):
    
    import gEvent
    
    if button == BUTTON_LEFT:
        if _state.session.unitsByLoc.has_key((maploc[0], maploc[1])):
            showArmyInfo()
            selectUnit(_state.session.unitsByLoc[(maploc[0], maploc[1])])
        else:
            _state.selectedUnit = None
            hideArmyInfo()
    
    elif button == BUTTON_LEFT + BUTTON_CONTROL:
        pass
    
    elif button == BUTTON_RIGHT:
        if _state.selectedUnit is not None:
            issueMoveOrder(_state.selectedUnit.ID, (maploc[0], maploc[1]))
            updateUnitInfo()

def mouseRelease(screenPosition, maploc, button):
    _state.mouseButton = None

def mouseMoveResponse(x, y):

    screenPosition = (x, y)
    displacement = map(lambda p,m,d: p/d - m/d, screenPosition, _state.mousePosition,  (getZoom(), getZoom()))
    
    mapCoord = map(lambda p,c,d: p/d - c/d, screenPosition, cameraPosition(), (getZoom(), getZoom()))
    mapGrid = [int(mapCoord[0]//32), int(mapCoord[1]//32)]
    
    mouseMove(screenPosition, mapGrid, displacement)
    
    _state.mousePosition = screenPosition

def mousePressResponse(x, y, t):

    screenPosition = (x, y)
    
    _state.mousePosition = screenPosition
    _state.mouseButton = t
    
    mapCoord = map(lambda p,c,d: p/d - c/d, screenPosition, cameraPosition(), (getZoom(), getZoom()))
    mapGrid = [int(mapCoord[0]//32), int(mapCoord[1]//32)]
    
    mousePress(screenPosition, mapGrid, t)
    
def mouseReleaseResponse(x, y, t):
    
    screenPosition = (x, y)
    
    _state.mousePosition = screenPosition
    _state.mouseButton = t
    
    mapCoord = map(lambda p,c,d: p/d - c/d, screenPosition, cameraPosition(), (getZoom(), getZoom()))
    mapGrid = [int(mapCoord[0]//32), int(mapCoord[1]//32)]
    
    mouseRelease(screenPosition, mapGrid, t)

